home *** CD-ROM | disk | FTP | other *** search
/ HPAVC / HPAVC CD-ROM.iso / WINER.ZIP / CHAP1.TXT < prev    next >
Text File  |  1994-09-04  |  42KB  |  726 lines

  1.                                 CHAPTER 1
  2.  
  3.                     AN INTRODUCTION TO COMPILED BASIC
  4.  
  5. This chapter explores the internal workings of the BASIC compiler.  Many
  6. people view a compiler simply as a "black box" which magically transforms
  7. BASIC source files into executable code.  Of course, magic does not play
  8. a part in any computer program, and the BC compiler that comes with
  9. Microsoft BASIC is no exception.  It is merely a program that processes
  10. data in the same way any other program would.  In this case, the data is
  11. your BASIC source code.
  12.    You will learn here what the BASIC compiler does, and how it does it. 
  13. You will also get an inside glimpse at some of the decisions a compiler
  14. must make, as it transforms your code into the assembly language commands
  15. the CPU will execute.  By truly understanding the compiler's role, you will
  16. be able to exploit its strengths and also avoid its weaknesses.
  17.  
  18.  
  19. COMPILER FUNDAMENTALS
  20. =====================
  21.  
  22. No matter what language a program is written in, at some point it must be
  23. translated into the binary codes that the PC's processor can understand. 
  24. Unlike BASIC commands, the CPU within every PC is capable of acting on only
  25. very rudimentary instructions.  Some typical examples of these instructions
  26. are "Add 3 to the value stored in memory location 100", and "Compare the
  27. value stored at address 4012 to the number -12 and jump to the code at
  28. address 2015 if it is less".  Therefore, one very important value of a
  29. high-level language such as BASIC is that a programmer can use meaningful
  30. names instead of memory addresses when referring to variables and
  31. subroutines.  Another is the ability to perform complex actions that
  32. require many separate small steps using only one or two statements.
  33.    As an example, when you use the command PRINT X% in a program, the value
  34. of X% must first be converted from its native two-byte binary format into
  35. an ASCII string suitable for display.  Next, the current cursor location
  36. must be determined, at which point the characters in the string are placed
  37. into the screen's memory area.  Further, the cursor position has to be
  38. updated, to place it just past the digits that were printed.  Finally, if
  39. the last digit happened to end up at the bottom-right corner of the screen,
  40. the display must also be scrolled up a line.  As you can see, that's an
  41. awful lot of activity for such a seemingly simple statement!
  42.    A compiler, then, is a program that translates these English-like BASIC
  43. source statements into the many separate and tiny steps the microprocessor
  44. requires.  The BASIC compiler has four major responsibilities, as shown in
  45. Figure 1-1 below.
  46.  
  47.  
  48.    1. Translate BASIC statements into an equivalent series of assembly
  49.    language commands.
  50.  
  51.    2. Assign addresses in memory to hold each of the variables being used
  52.    by the program.
  53.  
  54.    3. Remember the addresses in the generated code where each line number
  55.    or label occurs, for GOTO and GOSUB statements.
  56.  
  57.    4. Generate additional code to test for events and detect errors when
  58.    the /v, /w, or /d compile options are used.
  59.  
  60. Figure 1-1: The primary actions performed by a BASIC compiler.
  61.  
  62.  
  63. As the compiler processes a program's source code, it translates only the
  64. most basic statements directly into assembly language.  For other, more
  65. complex statements, it instead generates calls to routines in the BASIC
  66. run-time library that is supplied with your compiler.  When designing a
  67. BASIC program you would most likely identify operations that need to be
  68. performed more than once, and then create subprograms or functions rather
  69. than add the same code in-line repeatedly.  Likewise, the compiler takes
  70. advantage of the inherent efficiency of using called subroutines.
  71.    For example, when you use a BASIC statement such as PRINT Work$, the
  72. compiler processes it as if you had used CALL PRINT(Work$).  That is, PRINT
  73. really is a called subroutine.  Similarly, when you write OPEN FileName$
  74. FOR RANDOM AS #1 LEN = 1024, the compiler treats that as a call to its Open
  75. routine, and it creates code identical to CALL OPEN(FileName$, 1, 1024, 4). 
  76. Here, the first argument is the file name, the second is the file number
  77. you specified, the third is the record length, and the value 4 is BASIC's
  78. internal code for RANDOM.  Because these are BASIC key words, the CALL
  79. statement is of course not required.  But the end result is identical.
  80.    While the BC compiler could certainly create code to print the string
  81. or open the file directly, that would be much less efficient than using
  82. subroutines.  Indeed, all of the subroutines in the Microsoft-supplied
  83. libraries are written in assembly language for the smallest size and
  84. highest possible performance.
  85.  
  86.  
  87. DATA STORAGE
  88.  
  89. The second important job the compiler must perform is to identify all of
  90. the variables and other data your program is using, and allocate space for
  91. them in the object file.  There are two kinds of data that are manipulated
  92. in a BASIC program--static data and dynamic data.  The term static data
  93. refers to any variable whose address and size does not change during the
  94. execution of a program.  That is, all simple numeric and TYPE variables,
  95. and static numeric and TYPE arrays.  String constants such as "Press a key
  96. to continue" and DATA items are also considered to be static data, since
  97. their contents never change.
  98.    Dynamic data is that which changes in size or location when the program
  99. runs.  One example of dynamic data is a dynamic array, because space to
  100. hold its contents is allocated when the program runs.  Another is string
  101. data, which is constantly moved around in memory as new strings are
  102. assigned and old ones are erased.  Variable and array storage is discussed
  103. in depth in Chapter 2, so I won't belabor that now.  The goal here is
  104. simply to introduce the concept of variable storage.  The important point
  105. is that BC deals only with static data, because that must be placed into
  106. the object file.
  107.    As the compiler processes your source code, it must remember each
  108. variable that is encountered, and allocate space in the object file to hold
  109. it.  Further, all of this data must be able to fit into a single 64K
  110. segment, which is called DGROUP (for Data Group).  Although the compiled
  111. code in each object file may be as large as 64K, static data is combined
  112. from all of the files in a multi-module program, and may not exceed 64K in
  113. total size.  Note that this limitation is inherent in the design of the
  114. Intel microprocessors, and has nothing to do with BC, LINK, or DOS.
  115.    As each new variable is encountered, room to hold it is placed into the
  116. next available data address in the object file.  (In truth, the compiler
  117. retains all variable information in memory, and writes it to the end of the
  118. file all at once following the generated code.)  For each integer variable,
  119. two bytes are set aside.  Long integer and single precision variables
  120. require four bytes each, while double precision variables occupy eight
  121. bytes.  Fixed-length string and TYPE variables use a varying number of
  122. bytes, depending on the components you have defined.
  123.    Static numeric and TYPE arrays are also written to the object file by
  124. the compiler.  The number of bytes that are written of course depends on
  125. how many elements have been specified in the DIM statement.  Also, notice
  126. that no matter what type of variable or array is encountered, only zeroes
  127. are written to the file.  The only exceptions are quoted string constants
  128. and DATA items, in which case the actual text must be stored.
  129.    Unlike numeric, TYPE, and fixed-length variables, strings must be
  130. handled somewhat differently.  For each string variable a program uses, a
  131. four-byte table called a *string descriptor* is placed into the object
  132. file.  However, since the actual string data is not assigned until the
  133. program is run, space for that data need not be handled by the compiler. 
  134. With string arrays--whether static or dynamic--a table of four-byte
  135. descriptors is allocated.
  136.    Finally, each array in the program also requires an array descriptor. 
  137. This is simply a table that shows where the array's data is located in
  138. memory, how many elements it currently holds, the length in bytes of each
  139. element, and so forth.
  140.  
  141.  
  142. ASSEMBLY LANGUAGE CONSIDERATIONS
  143.  
  144. In order to fully appreciate how the translation process operates, you will
  145. first need to understand what assembly language is all about.  Please
  146. understand that there is nothing inherently difficult about assembly
  147. language.  Like BASIC, assembly language is comprised of individual
  148. instructions that are executed in sequence.  However, each of these
  149. instructions does much less than a typical BASIC statement.  Therefore,
  150. many more steps are required to achieve a given result than in a high-level
  151. language.  Some of these steps will be shown in the following examples. 
  152. If you are not comfortable with the idea of tackling assembly language
  153. concepts just yet, please feel free to come back to this section at a later
  154. time.
  155.    Let's begin by examining some very simple BASIC statements, and see how
  156. they are translated by the compiler.  For simplicity, I will show only
  157. integer math operations.  The 80x86 family of microprocessors can
  158. manipulate integer values directly, as opposed to single and double
  159. precision numbers which are much more complex.  The short code fragment in
  160. Listing 1-1 shows some very simple BASIC instructions, along with the
  161. resulting compiled assembly code.  In case you are interested,
  162. disassemblies such as those you are about to see are easy to create for
  163. yourself using the Microsoft CodeView utility.  CodeView is included with
  164. the Macro Assembler as well as with BASIC PDS.
  165.  
  166.  
  167. A% = 12
  168.    MOV  WORD PTR [A%],12    ;move a 12 into the word variable A%
  169.  
  170. X% = X% + 1
  171.    INC  WORD PTR [X%]       ;add 1 to the word variable X%
  172.  
  173. Y% = Y% + 100
  174.    ADD  WORD PTR [Y%],100   ;add 100 to the word variable Y%
  175.  
  176. Z% = A% + B%
  177.    MOV  AX,WORD PTR [B%]    ;move the contents of B% into AX
  178.    ADD  AX,WORD PTR [A%]    ;add to that the value of A%
  179.    MOV  WORD PTR [Z%],AX    ;move the result into Z%
  180.  
  181. Listing 1-1: These short examples show the compiled results of some simple
  182. BASIC math operations.
  183.  
  184.  
  185. The first statement, A% = 12, is directly translated to its assembler
  186. equivalent.  Here, the value 12 is *moved* into the word-sized address
  187. named A%.  Although an integer is the smallest data type supported by
  188. BASIC, the microprocessor can in fact deal with variables as small as one
  189. byte.  Therefore, the WORD PTR (word pointer) argument is needed to specify
  190. that A% is a full two-byte integer, rather than a single byte.  Notice that
  191. in assembly language, brackets are used to specify the contents of a memory
  192. address.  This is not unlike BASIC's PEEK() function, where parentheses are
  193. used for that purpose.
  194.    In the second statement, X% = X% + 1, the compiler generates assembly
  195. language code to increment, or add 1 to, the word-sized variable in the
  196. location named X%.  Since adding or subtracting a value of 1 is such a
  197. common operation in all programming languages, the designers of the 80x86
  198. included the INC (and complementary DEC) instruction to handle that.
  199.    Y% = Y% + 100 is similarly translated, but in this case to assembler
  200. code that adds the value 100 to the word-sized variable at address Y%.  As
  201. you can see, the simple BASIC statements shown thus far have a direct
  202. assembly language equivalent.  Therefore, the code that BC creates is
  203. extremely efficient, and in fact could not be improved upon even by a human
  204. hand-coding those statements in assembly language.
  205.    The last statement, Z% = A% + B%, is only slightly more complicated than
  206. the others.  This is because separate steps are required to retrieve the
  207. contents of one memory location, before manipulating it and assigning the
  208. result to another location.  Here, the value held in variable B% is moved
  209. into one of the processor's registers (AX).  The value of variable A% is
  210. then added to AX, and finally the result is moved into Z%.  There are about
  211. a dozen registers within the CPU, and you can think of them as special
  212. variables that can be accessed very quickly.
  213.    The next example in Listing 1-2 shows how BASIC passes arguments to its
  214. internal routines, in this case PRINT and OPEN.  Whenever a variable is
  215. passed to a routine, what is actually sent is the address (memory location)
  216. of the variable.  This way, the routine can go to that address, and read
  217. the value that is stored there.  As in Listing 1-1, the BASIC source code
  218. is shown along with the resultant compiler-generated assembler
  219. instructions.
  220.    It may also be worth mentioning that the order in which the arguments
  221. are sent to these routines is determined by how the routines are designed. 
  222. In BASIC, if a SUB is designed to accept, say, three parameters in a
  223. certain order, then the caller must pass its arguments in that same order. 
  224. Parameters in assembler routines are handled in exactly the same manner. 
  225. Of course, any arbitrary order could be used, and what's important is
  226. simply that they match.
  227.  
  228.  
  229. PRINT Work$
  230.     MOV  AX,OFFSET Work$     ;move the address of Work$ into AX
  231.     PUSH AX                  ;push that onto the CPU stack
  232.     CALL B$PESD              ;call the string printing routine
  233.  
  234. OPEN FileName$ FOR OUTPUT AS #1
  235.     MOV  AX,OFFSET FileName$ ;load the address of FileName$
  236.     PUSH AX                  ;push that onto the stack
  237.     MOV  AX,1                ;load the specified file number
  238.     PUSH AX                  ;and push that as well
  239.     MOV  AX,-1               ;-1 means that a LEN= was not given
  240.     PUSH AX                  ;and push that
  241.     MOV  AX,2                ;2 is the internal code for OUTPUT
  242.     PUSH AX                  ;pass that on too
  243.     CALL B$OPEN              ;finally, call the OPEN routine
  244.  
  245. Listing 1-2: Many BASIC statements create assembler code that passes
  246. arguments to internal routines, as shown above.
  247.  
  248.  
  249. When you tell BASIC to print a string, it first loads the address of the
  250. string into AX, and then pushes that onto the stack.  The stack is a
  251. special area in memory that all programs can access, and it is often used
  252. in compiled languages to hold the arguments being sent to subroutines.  In
  253. this case, the OFFSET operator tells the CPU to obtain the address where
  254. the variable resides, as opposed to the current contents of the variable. 
  255. Notice that the words offset, address, and memory location all mean the
  256. same thing.  Also notice that calls in assembly language work exactly the
  257. same as calls in BASIC.  When the called routine has finished, execution
  258. in the main program resumes with the next statement in sequence.
  259.    Once the address for Work$ has been pushed, BASIC's B$PESD routine is
  260. called.  Internally, one of the first things that B$PESD does is to
  261. retrieve the incoming address from the stack.  This way it can locate the
  262. characters that are to be printed.  B$PESD is responsible for printing
  263. strings, and other BASIC library routines are provided to print each type
  264. of data such as integers and single precision values.
  265.    In case you are interested, PESD stands for Print End-of-line String
  266. Descriptor.  Had a semicolon been used in the print statement--that is,
  267. PRINT Work$;--then B$PSSD would have been called instead (Print Semicolon
  268. String Descriptor).  Likewise, printing a 4-byte long integer with a
  269. trailing comma as in PRINT Value&, would result in a call to B$PCI4 (Print
  270. Comma Integer 4), where the 4 indicates the integer's size in bytes.
  271.    In the second example of Listing 1-2 the OPEN routine is set up and
  272. called in a similar fashion, except that four parameters are required
  273. instead of only one.  Again, each parameter is pushed onto the stack in
  274. turn, followed by a call to the routine.  Most of BASIC's internal routines
  275. begin with the characters "B$", to avoid a conflict with subroutines of
  276. your own.  Since a dollar sign is illegal in a BASIC procedure name, there
  277. is no chance that you will inadvertently choose one of the same names that
  278. BASIC uses.
  279.    As you can see, there is nothing mysterious or even difficult about
  280. assembly language, or the translations performed by the BASIC compiler. 
  281. However, a sequence of many small steps is often needed to perform even
  282. simple calculations and assignments.  We will discuss assembly language in
  283. much greater depth in Chapter 14, and my purpose here is merely to present
  284. the underlying concepts.
  285.    Please note that variable names are not retained after a program has
  286. been compiled.  Once BC has finished its job, all references to each
  287. variable name have been replaced with an equivalent memory addresses in the
  288. object file.  Further, once LINK has joined the object files and linked
  289. them to the BASIC language libraries, the procedure names are lost as well. 
  290. These issues will be explored in much greater detail in Chapter 14.
  291.  
  292.  
  293. COMPILER DIRECTIVES
  294.  
  295. As you have seen, some code is translated by the compiler into the
  296. equivalent assembly language statements, while other code is instead
  297. converted to calls to the language routines in the BASIC libraries.  Some
  298. statements, however, are not translated at all.  Rather, they are known as
  299. *compiler directives* that merely provide information to the compiler as
  300. it works.  Some examples of these non-executable BASIC statements include
  301. DEFINT, OPTION BASE, and REM, as well as the various "metacommands" such
  302. as '$INCLUDE and '$DYNAMIC.  Some others are SHARED, BYVAL, DATA, DECLARE,
  303. CONST, and TYPE.
  304.    For our purposes here, it is important to understand that DIM when used
  305. on a static array is also a non-executable statement.  Because the size of
  306. the array is known when the program is compiled, BC can simply set aside
  307. memory in the object file to hold the array contents.  Therefore, code does
  308. not need to be generated to actually create the array.  Similarly, TYPE/END
  309. TYPE statements also merely define a given number of bytes that will
  310. ultimately end up in the program file when the TYPE variable is later
  311. dimensioned by your program.
  312.  
  313.  
  314. EVENT AND ERROR CHECKING
  315.  
  316. The last compiler responsibility I will discuss here is the generation of
  317. additional code to test for events and debugging errors.  This occurs
  318. whenever a program is compiled using the /d, /w, or /v command line
  319. switches.  Although event trapping and debugging are entirely separate
  320. issues, they are handled in a similar manner.  Let's start with event
  321. trapping.
  322.    When the IBM PC was first introduced, the ability to handle interrupt-
  323. driven events distinguished it from its then-current Apple and Commodore
  324. counterparts.  Interrupts can provide an enormous advantage over polling
  325. methods, since polling requires a program to check constantly for, say,
  326. keyboard or communications activity.  With polling, a program must
  327. periodically examine the keyboard using INKEY$, to determine if a key was
  328. pressed.  But when interrupts are used, the program can simply go about its
  329. business, confident that any keystrokes will be processed.  Here's how that
  330. works:
  331.    Each time a key is pressed on a PC, the keyboard generates a hardware
  332. interrupt that suspends whatever is currently happening and then calls a
  333. routine in the ROM BIOS.  That routine in turn reads the character from the
  334. keyboard's output port, places it into the PC's keyboard buffer, and
  335. returns to the interrupted application.  The next time a program looks for
  336. a keystroke, that key is already waiting to be read.  For example, a
  337. program could begin writing a huge multi-megabyte disk file, and any
  338. keystrokes will still be handled even if the operator continues to type.
  339.    Understand that hardware interrupts are made possible by a direct
  340. physical connection between the keyboard circuitry and the PC's
  341. microprocessor.  The use of interrupts is a powerful concept, and one which
  342. is important to understand.  Unfortunately, BASIC does not use interrupts
  343. in most cases, and this discussion is presented solely in the interest of
  344. completeness.
  345.  
  346.  
  347. Event Trapping
  348.  
  349. BASIC provides a number of event handling statements that perhaps *could*
  350. be handled via interrupts, but aren't.  When you use ON TIMER, for example,
  351. code is added to periodically call a central event handler to check if the
  352. number of seconds specified has elapsed.  Because there are so many
  353. possible event traps that could be active at one time, it would be
  354. unreasonable to expect BASIC to set up separate interrupts to handle each
  355. possibility.  In some situations, such as ON KEY, there is a corresponding
  356. interrupt.  In this case, the keyboard interrupt.  However, some events
  357. such as ON PLAY(Count), where a GOSUB is made whenever the PLAY buffer has
  358. fewer than Count characters remaining, have no corresponding physical
  359. interrupt.  Therefore, polling for that condition is the only reasonable
  360. method.
  361.    The example in Listing 1-3 shows what happens when you compile using the
  362. /v switch.  Notice that the calls to B$EVCK (Event Check) are not part of
  363. the original source code.  Rather, they show the additional code that BC
  364. places just before each program statement.
  365.  
  366.  
  367. DEFINT A-Z
  368.     CALL B$EVCK              'this call is generated by BC
  369. ON TIMER(1) GOSUB HandleTime
  370.     CALL B$EVCK              'this call is generated by BC
  371. TIMER ON
  372.     CALL B$EVCK              'this call is generated by BC
  373. X = 10
  374.     CALL B$EVCK              'this call is generated by BC
  375. Y = 100
  376.     CALL B$EVCK              'this call is generated by BC
  377. END
  378.  
  379. HandleTime:
  380.     CALL B$EVCK              'this call is generated by BC
  381. BEEP
  382.     CALL B$EVCK              'this call is generated by BC
  383. RETURN
  384.  
  385. Listing 1-3: When the /v compiler switch is used, BC generates calls to a
  386. central event handler at each BASIC statement.
  387.  
  388.  
  389. At five bytes per call, you can see that using /v can quickly bloat a
  390. program to an unacceptable size.  One alternative is to instead use /w. 
  391. In fact, /w can be particularly attractive in those cases where event
  392. handling cannot be avoided, because it lets you specify where a call to
  393. B$EVCK is made: at each line label or line number in your source code.  The
  394. only downside to using line numbers and labels is that additional working
  395. memory is needed by BC to remember the addresses in the code where those
  396. labels are placed.  This is not usually a problem, though, unless the
  397. program is very large or every line is labeled.
  398.    All of the various BASIC event handling commands are specified using the
  399. ON statement.  It is important to understand, however, that ON GOTO and ON
  400. GOSUB do not involve events.  That is, they are really just an alternate
  401. form of GOTO and GOSUB respectively, and thus do not require compiling with
  402. /w or /v.
  403.  
  404.  
  405. Error Trapping
  406.  
  407. The last compiler option to consider here is the /d switch, because it too
  408. generates extra code that you might not otherwise be aware of.  When a
  409. program is compiled with /d, two things are added.  First, for every BASIC
  410. statement a call is made to a routine named B$LINA, which merely checks to
  411. see if Ctrl-Break has been pressed.  Normally, a compiled BASIC program is
  412. immune to pressing the Ctrl-C and Ctrl-Break keys, except during an INPUT
  413. or LINE INPUT statement.  Since much of the purpose of a debugging mode is
  414. to let you break out of an errant program gone berserk, the Ctrl-Break
  415. checking must be performed frequently.  These checks are handled in much
  416. the same way as event trapping, by calling a special routine once for each
  417. line in your source code.
  418.    Another important factor resulting from the use of /d is that all array
  419. references are handled through a special called routine which ensures that
  420. the element number specified is in fact legal.  Many people don't realize
  421. this, but when a program is compiled without /d and an invalid element is
  422. given, BASIC will blindly write to the wrong memory locations.  For
  423. example, if you use DIM Array%(1 TO 100) and then attempt to assign, say,
  424. element number 200, BASIC is glad to oblige.  Of course, there *is* no
  425. element 200 in that case, and some other data will no doubt be overwritten
  426. in the process.
  427.    To prevent these errors from going undetected, BC calls the B$HARY (Huge
  428. Array) routine to calculate the address based on the element number
  429. specified.  If B$HARY determines that the array reference is out of bounds,
  430. it invokes an internal error handler and you receive the familiar
  431. "Subscript out of range" message.  Normally, the compiler accesses array
  432. elements using as little code as possible, to achieve the highest possible
  433. performance.  If a static array is dimensioned to 100 elements and you
  434. assign element 10, BC knows at the time it compiles your program the
  435. address at which that element resides.  It can therefore access that
  436. element directly, just as if it were a non-array variable.
  437.    Even when you use a variable to specify an array element such as
  438. Array%(X) = 12, the starting address of the array is known, and the value
  439. in X can be used to quickly calculate how far into the array that element
  440. is located.  Therefore, the lack of bounds checking in programs that do not
  441. use /d is not a bug in BASIC.  Rather, it is merely a trade-off to obtain
  442. very high performance.  Indeed, one of the primary purposes of using /d is
  443. to let BC find mistakes in your programs during development, though at the
  444. cost of execution speed.
  445.    The biggest complication from BASIC's point of view is when huge
  446. (greater than 64K) arrays are being manipulated.  In fact, B$HARY is the
  447. very same routine that BC calls when you use the /ah switch to specify huge
  448. arrays (hence the name HARY).  Since extra code is needed to set up and
  449. call B$HARY compared to the normal array access, using /ah also creates
  450. programs that are larger and slower than when it is not used.  Further,
  451. because B$HARY is used by both /d and /ah, invalid element accesses will
  452. also be trapped when you compile using /ah.
  453.  
  454.  
  455. Overflow Errors
  456.  
  457. The final result of using /d is that extra code is generated after certain
  458. math operations, to check for overflow errors that might otherwise go
  459. undetected.  Overflow errors are those that result in a value too large for
  460. a given data type.  For example,
  461. if you multiply two integers and the result exceeds 32767, that causes an
  462. overflow error.  Similarly, an underflow error would be created by a
  463. calculation resulting a value that is too small.
  464.    When a floating point math operation is performed, errors that result
  465. from overflow are detected by the routines that perform the calculation. 
  466. When that happens there is no recourse other than halting your program with
  467. an appropriate message.  Integer operations, however, are handled directly
  468. by 80x86 instructions.  Further, an out of bounds result is not necessarily
  469. illegal to the CPU.  Thus, programs compiled without the /d option can
  470. produce erroneous results, and without any indication that an error
  471. occurred.
  472.    To prove this to yourself, compile and run the short program shown in
  473. Listing 1-4, but without using /d.  Although the correct result should be
  474. 90000, the answer that is actually displayed is 24464.  And you will notice
  475. that no error message is displayed!
  476. As with illegal array references, BC would rather optimize for speed, and
  477. give you the option of using /d as an aid for tracking down such errors as
  478. they occur.  If you compile the program in Listing 1-4 with the /d option,
  479. then BASIC will report the error as expected.
  480.    Since an overflow resulting from integer operations is not technically
  481. an error as far as the CPU is concerned, how, then, can BASIC trap for
  482. that?  Although an error in the usual sense is not created, there is a
  483. special flag variable within the CPU that is set whenever such a condition
  484. occurs.  Further, a little-used assembler instruction, INTO (Interrupt 4
  485. if Overflow), will generate software Interrupt 4 if that flag is set. 
  486. Therefore, all BC has to do is create an Interrupt 4 handler, and then
  487. place an INTO instruction after every integer math operation in the
  488. compiled code.  The interrupt handler will receive control and display an
  489. "Overflow" message whenever an INTO calls it.  Since the INTO instruction
  490. is only one byte and is also very fast, using it this way results in very
  491. little size or performance degradation.
  492.  
  493.  
  494. X% = 30000
  495. Y% = X% * 10
  496. PRINT Y%
  497.  
  498. Listing 1-4: This brief program illustrates how overflow errors are handled
  499. in BASIC.
  500.  
  501.  
  502. COMPILER OPTIMIZATION
  503.  
  504. Designing a compiler for a language as complex as BASIC involves some very
  505. tricky programming indeed.  Although it is one thing to translate a BASIC
  506. source file into a series of assembly language commands, it is another
  507. matter entirely to do it well!  Consider that the compiler must be able to
  508. accept a BASIC statement such as X! = ABS(SQR((Y# + Z!) ^ VAL(Work$))), and
  509. reduce that to the individual steps necessary to arrive at the correct
  510. result.
  511.    Many, many details must be accounted for and handled, not the least of
  512. which are syntax or other errors in the source code.  Moreover, there are
  513. an infinite number of ways that a programmer can accomplish the same thing. 
  514. Therefore, the compiler must be able to recognize many different
  515. programming patterns, and substitute efficient blocks of assembler code
  516. whenever it can.  This is the role of an *optimizing compiler*.
  517.    One important type of optimization is called *constant folding*.  This
  518. means that as much math as possible is performed during compilation, rather
  519. than creating code to do that when the program runs.  For example, if you
  520. have a statement such as X = 4 * Y * 3 BC can, and does, change that to X
  521. = Y * 12.  After all, why multiply 3 times 4 later, when the answer can be
  522. determined now?  This substitution is performed entirely by the BC
  523. compiler, without your knowing about it.
  524.    Another important type of optimization is BASIC's ability to remember
  525. calculations it has already performed, and use the results again later if
  526. possible.  BC is especially brilliant in this regard, and it can look ahead
  527. many lines in your source code for a repeated use of the same calculations. 
  528. Listing 1-5 shows a short fragment of BASIC source code, along with the
  529. resultant assembler output.
  530.  
  531.  
  532. X% = 3 * Y% * 4
  533.     MOV  AX,12               ;move the value 12 into AX
  534.     IMUL WORD PTR [Y%]       ;Integer-Multiply that times Y%
  535.     MOV  WORD PTR [X%],AX    ;assign the result in AX to X%
  536.  
  537. A% = S% * 100
  538.     MOV  BX,AX               ;save the result from above in BX
  539.     MOV  AX,100              ;then assign AX to 100
  540.     IMUL WORD PTR [S%]       ;now multiply AX times S%
  541.     MOV  WORD PTR [A%],AX    ;and assign A% from the result
  542.  
  543. Z% = Y% * 12
  544.     MOV  WORD PTR [Z%],BX    ;assign Z% from the earlier result
  545.  
  546. Listing 1-5: These short code fragments illustrate how adept BC is at
  547. reusing the result of earlier calculations already performed.
  548.  
  549.  
  550. As you can see in the first part of Listing 1-5, the value of 3 times 4 was
  551. resolved to 12 by the compiler.  Code was then generated to multiply the
  552. 12 times Y%, and the result is in turn assigned to X%.  This is similar to
  553. the compiled code examined earlier in Listing 1-1.  Notice, however, that
  554. before the second multiplication of S% is performed, the result currently
  555. in AX is saved in the BX register.  Although AX is destroyed by the
  556. subsequent multiplication of S% times 100, the result that was saved
  557. earlier in BX can be used to assign Z% later on.  Also notice that even
  558. though 3 * 4 was used first, BC was smart enough to realize that this is
  559. the same as the 12 used later.
  560.    While the compiler can actually look ahead in your source code as it
  561. works, such optimization will be thwarted by the presence of line numbers
  562. and labels, as well as IF blocks.  Since a GOTO or GOSUB could jump to a
  563. labeled source line from anywhere in the program, there is no way for BC
  564. to be sure that earlier statements were executed in sequence.  Likewise,
  565. the compiler has no way to know which path in an IF/ELSE block will be
  566. taken at run time, and thus cannot optimize across those statements.
  567.  
  568.  
  569. THE BASIC RUN-TIME LIBRARIES
  570.  
  571. Microsoft compiled BASIC lets you create two fundamentally different types
  572. of programs.  Those that are entirely self-contained in one .EXE file are
  573. compiled with the /o command line switch.  In this case, the compiler
  574. creates translations such as those we have already discussed, and also
  575. generates calls to the BASIC language routines contained in the library
  576. files supplied by Microsoft.  When your compiled program is subsequently
  577. linked, only those routines that are actually used will be added to your
  578. program.
  579.    When /o is not used, a completely different method is employed.  In this
  580. case, a special .EXE file that contains support for every BASIC statement
  581. is loaded along with the BASIC program when the program is run from the DOS
  582. command line.  As you are about to see, there are advantages and
  583. disadvantages to each method.  For the purpose of this discussion I will
  584. refer to stand-alone programs as BCOM programs, after the BCOMxx.LIB
  585. library name used in all versions of QuickBASIC.  Programs that instead
  586. require the BRUNxx.LIB library to be present at run time will be called
  587. BRUN programs.
  588.    Beginning with BASIC 7 PDS, the library naming conventions used by
  589. Microsoft have become more obscure.  This is because PDS includes a number
  590. of variations for each method, depending on the type of "math package" that
  591. is specified when compiling and whether you are compiling a program to run
  592. under DOS or OS/2.  These variations will be discussed fully in Chapter 6,
  593. when we examine all of the possible options that each compiler version has
  594. to offer.  But for now, we will consider only the two basic methods--BCOM
  595. and BRUN.  The primary differences between these two types of programs are
  596. shown in Figure 1-2.
  597.  
  598.  
  599.    1.  BCOM programs require less memory, run faster, and do   not require
  600.    the presence of the BRUNxx.EXE file when the program is run.
  601.  
  602.    2.  BRUN programs occupy less disk space, and also allow subsequent
  603.    chaining to other programs that can share the common library code which
  604.    is already resident.  Chained-to programs also load quickly because the
  605.    BRUN library is already in memory.
  606.  
  607. Figure 1-2: A comparison of the fundamental differences between BCOM and
  608. BRUN programs.
  609.  
  610.  
  611. Stand-alone BCOM programs are always larger than an equivalent BRUN program
  612. because the library code for PRINT, INSTR, and so forth is included in the
  613. final .EXE file.  However, less *memory* will be required when the program
  614. runs, since only the code that is really needed is loaded into the PC. 
  615. Likewise, a BRUN program will take less disk space, because it contains
  616. only the compiled code.  The actual routines to handle each BASIC
  617. statements are stored in the BRUNxx.LIB library, and that library is loaded
  618. automatically when the main program is run from DOS.
  619.    You might think that since a BRUN program is physically smaller on disk
  620. it will load faster, but this is not necessarily true.  When you execute
  621. a BRUN program from the DOS command line, one of the first things it does
  622. is load the BRUN .EXE support file.  Since this support file is fairly
  623. large, the overall load time will be much greater than the compiled BASIC
  624. program's file size would indicate.  However, if the main program
  625. subsequently chains to another BASIC program, that program will load
  626. quickly because the BRUN file does not need to be loaded a second time.
  627.    One other important difference between these two methods is the way that
  628. the BASIC language routines are accessed.  When a BCOM program is compiled
  629. and linked, the necessary routines are called in the usual fashion.  That
  630. is, the compiler generates code that calls the routines in the BCOM library
  631. directly.  When the program is subsequently linked, the procedure names are
  632. translated by LINK into the equivalent memory addresses.  That is, a call
  633. to PRINT is in effect translated from CALL B$PESD to CALL ####:####, where
  634. ####:#### is a segment and address.
  635.    BRUN programs, on the other hand, instead use a system of interrupts to
  636. access the BASIC language routines.  Since there is no way for LINK to know
  637. exactly where in memory the BRUNxx.EXE file will be ultimately loaded, the
  638. interrupt vector table located in low memory is used to hold the various
  639. routine addresses.  Although many of these interrupt entries are used by
  640. the PC's system resources, many others are available.  Again, I will defer
  641. a thorough treatment of call methods and interrupts until Chapter 14.  But
  642. for now, suffice it to say that a direct call is slightly faster than an
  643. indirect call, where the address to be called must first be retrieved from
  644. a table.
  645.    As an interesting aside, the routines in the BRUNxx.EXE file in fact
  646. modify the caller's code to perform a direct call, rather than an interrupt
  647. instruction.  Therefore, the first time a given block of code is executed,
  648. it calls the run-time routines through an interrupt instruction. 
  649. Thereafter, the address where the BRUN file has been loaded is known, and
  650. will be used the next time that same block of code is executed.  In
  651. practice, however, this improves only code that lies within a FOR/NEXT,
  652. WHILE, or DO loop.  Further, code that is executed only once will actually
  653. be much slower than in a BCOM program, because of the added self-
  654. modification (the program changes itself) instructions.
  655.    Notice that when BC compiles your program, it places the name of the
  656. appropriate library into the object file.  The name BC uses depends on
  657. which compiler options were given.  This way you don't have to specify the
  658. correct name manually, and LINK can read that name and act accordingly. 
  659. Although QuickBASIC provides only two libraries--one for BCOM programs and
  660. one for BRUN--BASIC PDS offers a number of additional options.  Each of
  661. these options requires the program to be linked with a different library. 
  662. That is, there are both BRUN and BCOM libraries for use with OS/2, for near
  663. and far strings, and for using IEEE or Microsoft's alternate math
  664. libraries.  Yet another library is provided for 8087-only operation.
  665.  
  666.  
  667. GRANULARITY
  668.  
  669. Until now, we have examined only the actions and methods used by the BC
  670. compiler.  However, the process of creating an .EXE file that can be run
  671. from the DOS command line is not complete until the compiled object file
  672. has been linked to the BASIC libraries.  I stated earlier that when a
  673. stand-alone program is created using the /o switch, only those routines in
  674. the BCOM library that are actually needed will be added to the program. 
  675. Unfortunately, that is not entirely accurate.  While it is true that LINK
  676. is very smart and will bring in only those routines that are actually
  677. called, there is one catch.
  678.    Imagine that you have written a BASIC program which is comprised of two
  679. separate modules.  In one file is the main program that contains only in-
  680. line code, and in the other are two BASIC subprograms.  Even if the main
  681. program calls only one of those subprograms, both will be added when the
  682. program is linked.  That is, LINK can resolve routines to the source file
  683. level only, but cannot extract a single routine from an object module which
  684. contains multiple routines.  Since an .LIB library file is merely a
  685. collection of separate object modules, all of the routines that reside in
  686. a given module will be added to a program, even if only one has been
  687. accessed.  This property is called *granularity*, and it determines how
  688. finely LINK can remove routines from a library.
  689.    In the case of the libraries supplied with BASIC, the determining factor
  690. is which assembly language routines were combined with which other routines
  691. in the same source file by the programmers at Microsoft.  In QuickBASIC
  692. 4.5, for example, when a program uses the CLS statement, the routines that
  693. handle COLOR, CSRLIN, POS(0), LOCATE, and the function form of SCREEN are
  694. also added.  This is true even if none of those other statements have been
  695. used.  Fortunately, Microsoft has done much to improve this situation in
  696. BASIC PDS, but there is still room for improvement.  In BASIC PDS, CLS is
  697. stored in a separate file, however POS(0), CSRLIN, and SCREEN are still
  698. together, as are COLOR and LOCATE.
  699.    Obviously, Microsoft has their reasons for doing what they do, and I
  700. won't attempt to second guess their expertise here.  The BASIC language
  701. libraries are extremely complex and contain many routines.  (The QuickBASIC
  702. 4.5 BCOM45.LIB file contains 1,485 separate assembler procedures.)  With
  703. such an enormous number of assembly language source files to deal with, it
  704. no doubt makes a lot of sense to organize the related routines together. 
  705. But it is worth mentioning that Crescent Software's P.D.Q. library can
  706. replace much of the functionality of the BCOM libraries, and with complete
  707. granularity.  In fact, P.D.Q. can create working .EXE programs from BASIC
  708. source that are less than 800 bytes in size.
  709.  
  710.  
  711. SUMMARY
  712. =======
  713.  
  714. In this chapter, you learned about the process of compiling, and the kinds
  715. of decisions a sophisticated compiler such as Microsoft BASIC must make. 
  716. In some cases, the BASIC compiler performs a direct translation of your
  717. BASIC source code into assembly language, and in others it creates calls
  718. to existing routines in the BCOM libraries.  Besides creating the actual
  719. assembler code, BASIC must also allocate space for all of the data used in
  720. a program.
  721.    You also learned some basics about assembly language, which will be
  722. covered in more detail in Chapter 13.  However, examples in upcoming
  723. chapters will also use brief assembly language examples to show the
  724. relative efficiency of different coding styles.  In Chapter 2, you will
  725. learn how variables and other data are stored in memory.
  726.